package hypeerweb;
import gui.Main.GUIhelper;
import gui.Main.GUIhelperProxy;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import states.HypercubeCapState;
import states.NodeState;

import communicator.GlobalObjectId;
import communicator.LocalObjectId;
import communicator.ObjectDB;

import database.HyPeerWebDatabase;

//HyPeerWeb:
//authors: Ike Ellsworth
//       Kent Barker
//Stores the information about all the nodes.
/** Description of HyPeerWeb
* 
*
* @author Ike Ellsworth
* @author Kent Barker
*/
public class HyPeerWeb  implements Serializable
{   
     private static final long serialVersionUID = 1L;
     private HashSet<Node> nodes;
     private static HyPeerWeb singleton = null;
     
     private List<GUIhelper> helpers=new ArrayList<GUIhelper>();
     
     private GlobalObjectId globalId;
     public boolean kill()
     {
         System.out.println("Goodbye, cruel world!");
         List<Integer> nodenums = new LinkedList<Integer>();
         for (Node n: nodes)
         {
             nodenums.add(n.getWebId().getValue());
         }
         for (Integer i: nodenums)
         {
             removeFromHyPeerWeb(getStartNode().getWebId().getValue());
         }
         for (Integer i: nodenums)
         {
             master.addToHyPeerWeb(master.getStartNode().getWebId().getValue());
         }
         return true;
     }
     public void set_helper(GlobalObjectId globalId)
     {
         helpers.add(new GUIhelperProxy(globalId));
     }
     public List<GUIhelper> get_helpers()
     {
         return helpers;
     }
     public void remove_helper(GlobalObjectId globalId)
     {  
         int index = 0;
         for(GUIhelper helper:helpers)
         {
             GlobalObjectId id = helper.get_globalId();
             if(id.getMachineAddr().toString().compareTo(globalId.getMachineAddr().toString()) == 0
                     && id.getPortNumber().toString().compareTo(globalId.getPortNumber().toString()) == 0
                     && id.getLocalObjectId().toString().compareTo(globalId.getLocalObjectId().toString()) == 0)
             {
                 helpers.remove(index);       
                 return;
             }
             index++;
         }
     }
     public GlobalObjectId get_GlobalId()
     {
         return globalId;
     }
     private HyPeerWeb master = null;
     private HashSet<HyPeerWeb> slaves; 
     public void connectSlave(GlobalObjectId slave)
     {
        slaves.add(new HyPeerWebProxy(slave));
     }
     public void removeSlave(GlobalObjectId slave)
     {
         slaves.remove(slave);
     }
     public void setMaster(HyPeerWeb newMaster)
     {
         master = newMaster;
         master.connectSlave(globalId);
     }
     public boolean isHyPeerWebEmpty()
     {
         if(nodes.size() > 0)
         {
             return false;
         }
         else
         {
             if(master != null)
             {
                 return master.isHyPeerWebEmpty();
             }
            
             for(HyPeerWeb slave :slaves)
             {
                 if(slave.size() > 0)
                 {
                     return false;
                 }
             }
         }
         
         return true;
     }
     
     //Kent made changes to this may fix problems or not
     public Node getStartNode()
     {
        if(size() > 0)
        {
            return (Node)(nodes.toArray())[0];
        }
        if(master != null)
        {
            return master.getStartNode();
        }
        else
        {
            for(HyPeerWeb slave: slaves)
            {
                Node node = slave.getStartNode();
                if(node != null)
                {
                    return node;
                }
            }
        }
        return null;
     }

     /** 
      * Description of HyPeerWeb
     * 
     *@pre neighbors &ne; null AND<br>
     *      upPointers &ne; null AND<br>
     *      downPointers &ne; null AND<br>
     *@post 
     * 
     */
     public static void set_port(int _port)
     {
         //port = _port;
     }
     private static LocalObjectId local_key;
     protected HyPeerWeb()
     {
             nodes = new HashSet<Node>();
             slaves = new HashSet<HyPeerWeb>();
             globalId = new GlobalObjectId();
       
     }
     /**
      * adds a node to a collection of Node in the Hypeerweb
      * @param Node node 
      * 
      * @pre node is not null
      * @post <i>None</i>
      */
     public void addNode(Node node)
     {          
              nodes.add(node);
     }
     public void addNode(int node_num)
     {
         Node node;
         if(node_num > 0)
         {
             node = getNode(node_num);
         }
         else
         {
             node = new Node(0);
             generateKeys(node);           
         }
         addNode(node);
     }
     public Node makeNode()
     {
         return new Node(0);
     }
     public void addToHyPeerWeb(int startNode_num)
     {
         Node newNode = new Node(0);
         generateKeys(newNode);
         Node startNode;
         if(startNode_num == -1)
         {
             if(isHyPeerWebEmpty())
             {
                 startNode = null;
             }
             else
             {
                 startNode = getStartNode();
             }
             
         }
         else
         {
             startNode = getNode(startNode_num);
         }
         addToHyPeerWeb(newNode,startNode);
     }
     /**
      * adds the newNode to the Hypeerweb with stating at the startNode
      * @param newNode startNode both are of type Node
      * 
      * @pre  newNode is not null
      *       newNode does not exist in the Hypeerweb
      *       startNode is not null
      *       startNode exists in the Hypeerweb
      * @post newNode is in the Hypeerweb
      *       all nodes that previously existed in the Hypeerweb remain in the Hypeerweb
      *       there is only one more node in the Hypeerweb
      *       
      */
   

     public void addToHyPeerWeb(Node newNode, Node startNode)
     {        
         assert(newNode != null);
         assert(nodes.isEmpty() || (startNode != null && nodes.contains(startNode)));
         assert(newNode != Node.NULL_NODE);
         assert(!nodes.contains(newNode));
         if(startNode == null && isHyPeerWebEmpty())
         {
             newNode.clearAll();
             newNode.setWebId(new WebId(0));
             newNode.setFold(newNode);             
             newNode.setState(NodeState.getNodeState(HypercubeCapState.STATE_ID));
             nodes.add(newNode);

             try
             {
                 Thread.sleep(250);
             }
             catch (InterruptedException e)
             {
                
                 e.printStackTrace();
             }
             for (GUIhelper guihelper : helpers)
             {
                 guihelper.addNodeGui(newNode.getWebId().getValue());
             }
             
             return;
         }
         Command add = new AddCommand();
         nodes.add(newNode);
         
         
         add.execute(newNode, startNode);

        try
        {
            Thread.sleep(250);
        }
        catch (InterruptedException e)
        {
           
            e.printStackTrace();
        }

        for (GUIhelper guihelper : helpers)
        {
            guihelper.addNodeGui(newNode.getWebId().getValue());
        }
         //cls
     }
     public static HyPeerWeb getHyPeerWeb(){
         return getSingleton();
     }
     public void close()
     {
         saveToDatabase();
         
     } 
     public void removeFromHyPeerWeb(int deleteNode_num)
     {
         Node deleteNode = getNode(deleteNode_num);
         removeFromHyPeerWeb(deleteNode);
         ObjectDB.getSingleton().remove(deleteNode.get_key());
     }
     public void removeFromHyPeerWeb(Node deleteNode)
     {
         assert(deleteNode != null);
         assert(!nodes.isEmpty());
         assert(nodes.contains(deleteNode));
         Command remove = new DeleteCommand();

         LocalObjectId deleteKey = deleteNode.get_key();
         
         remove.execute(deleteNode, deleteNode);
        
         nodes.remove(deleteNode);

         ObjectDB.getSingleton().remove(deleteKey);
         try
         {
             Thread.sleep(250);
         }
         catch (InterruptedException e)
         {
            
             e.printStackTrace();
         }
         deleteUpdateGui();
     }

    public void slaveDeleteUpdateGui()
    {
        
        ArrayList<Integer> set = new ArrayList<Integer>();
        for (Node node : nodes)
        {
            set.add(node.getWebId().getValue());
        }
        for (GUIhelper guihelper : helpers)
        {
            guihelper.updateNodeGui(set);        
        }
    }

    public void deleteUpdateGui()
    {

        if (master != null)
        {

            master.deleteUpdateGui();
            return;
        }
        else
        {
            slaveDeleteUpdateGui();
            for (HyPeerWeb slave : slaves)
            {
                slave.slaveDeleteUpdateGui();
            }
        }
    }
     /**
      * Clears the hypeerweb
      * 
      * 
      * @pre <i>None</i>
      * @post The Hypeerweb is empty
      */
     public void clearAll()
     {
         if(master != null)
         {
             master.clearAll();
         }
         else
         {
             clear();
             for(HyPeerWeb slave : slaves)
             {
                 slave.clear();
             }
         }
     }
     public void clear()
     {
          nodes.clear();
          for(GUIhelper helper:helpers)
          {
              helper.clearGui();
          }
              //cls
              //I need to write some code so that the object db has all the nodes cleared from it
              //db.clear();
     }
     /**
      * checks the Hypeerweb to see if node is contained within it.
      * @param node 
      * @return boolean returns true if node is found in Hypeerweb
      * 
      * @pre node is not null
      * @post <i>None</i>
      */
     public boolean contains(Node node)
     {
         
         for(Node n:nodes)
         {
             if(n == node)
             {
                 return true;
             }
         }
         return false;
         //return nodes.contains(node);
     }
     public boolean contains(int node_num)
     {
         Node node = getNode(node_num);
         for(Node n: nodes)
         {
             if(n.getWebId().getValue() == node_num)
                 return true;
         }
         if(node == null)
         {
             return false;
         }
         boolean temp = contains(node);

         return temp;
     }
     /**
      * returns the node in the hypeerweb with webId i
      * @param int i  
      * @return Node (or null if webId is not in the hypeerweb) this is the node with webId = i 
      * 
      * @pre is is greater than or equal to zero
      * @post hypeerweb is unchanged
      */
     public Node getNode(int node)
     {
          for(Node n:nodes)
          {
                  if(n.getWebId().getValue() == node)
                          return n;
          }
          if(master != null)
          {
             return master.getNode(node);
          }
          else
          {
              for(HyPeerWeb slave:slaves)
              {
                  Node value = slave.getSlaveNode(node);
                  if(value != null)
                  {
                      return value;
                  }
              }
          }
          return null;
     }
     public Node getSlaveNode(int node)
     {
          for(Node n:nodes)
          {
                  if(n.getWebId().getValue() == node)
                          return n;
          }
          
          return null;
     }
     /**
      * returns the Hypeerweb object 
      * @return HyPeerWeb 
      * 
      * @pre HyPeerWeb exists
      * @post <i>None</i>
      */
     public static HyPeerWeb getSingleton()
     {
             if(singleton == null)
             {
                     singleton = new HyPeerWeb();
                     ObjectDB.getSingleton().store(Node.NULL_NODE.getGlobalId().getLocalObjectId(), Node.NULL_NODE);
             }

             return singleton;
     }
     /**
      * reloads the default database
      * 
      * @pre <i>None</i>
      * @post database loads the Hypeerweb with the current information in the database
      */
     public void reload()
     {
         clear();
             HyPeerWebDatabase.initHyPeerWebDatabase();
             getNodesFromDatabase();
     }
     /**
      * reloads a database with the given name dbName
      * @param dbName 
      * 
      * @pre <i>None</i>
      * @post database loads the Hypeerweb with the current information in the given database
      */
     public void reload(java.lang.String dbName)
     {
         clear();
             HyPeerWebDatabase.initHyPeerWebDatabase(dbName);
             getNodesFromDatabase();
     }
     /**
      * removes the given node from the Hypeerweb
      * @param node 
      * 
      * @pre node exists in the Hypeerweb and is not null
      * @post node is removed from the Hypeerweb
      */
     public void removeNode(int node_num)
     {
         Node node = getNode(node_num);
         removeNode(node);
     }

     public void removeNode(Node node)
     {       
             //cls
             LocalObjectId deleteKey = node.get_key();
             nodes.remove(node);

             ObjectDB.getSingleton().remove(deleteKey);
     }
     /**
      * saves the current information that is in the Hypeerweb to the database
      * 
      * @pre the Hypeerweb exists
      * @post all data saved properly to the database
      */
     public void saveToDatabase()
     {
             HyPeerWebDatabase.initHyPeerWebDatabase();
             HyPeerWebDatabase database = HyPeerWebDatabase.getSingleton();
             database.clear();
             for(Node n: nodes)
             {
                     if(n instanceof NodeProxy)
                     {
                         database.addProxyNodeToDatabase(n.getGlobalId(),n.getWebId().getValue());    
                     }
                     else
                     {
                         database.addNodeToDatabase(n.constructSimplifiedNodeDomain());
                     }
             }
     }



     /**
      * returns the amount of nodes in the Hypeerweb
      * @return int 
      * 
      * @pre Hypeerweb is not null
      * @post <i>None</i>
      */
     public int size()
     {
         return nodes.size();
     }
     /**
      * gets Nodes for the database and prepares them for being connected
      * 
      * @pre <i>None</i>
      * @post <i>None</i>
      */
     private void getNodesFromDatabase()
     {
             HyPeerWebDatabase database = HyPeerWebDatabase.getSingleton();
             List<SimplifiedNodeDomain> nodeInfo = new LinkedList<SimplifiedNodeDomain>();
             int i = 0;
             while(i != -1)
             {
                     SimplifiedNodeDomain temp;
                     GlobalObjectId temp2;
                     try
                     {
                             temp = database.getNode(i);
                             temp2= database.getNodeProxy(i);
                             if(temp.webId != -1)
                             {
                                     nodeInfo.add(temp);
                                     Node temp_node = new Node(temp.webId, temp.height);
                                     generateKeys(temp_node);
                                     nodes.add(temp_node);
                                     i++;
                             }
                             else if(temp2.getPortNumber().getValue()!=-1)
                             {
                                 Node temp_node = new NodeProxy(temp2);
                                 nodes.add(temp_node);
                                 i++;
                             }
                             else
                             {
                                     i = -1;
                             }
                     }
                     catch (Exception e)
                     {
                             e.printStackTrace();
                             return;
                     }
             }
             connectNodes(nodeInfo);
     }
     /**
      * connects the nodes that were received by the database
      * @param List<SimplifiedNodeDomain> nodeInfo 
      * 
      * @pre nodeInfo is not null
      * @post <i>None</i>
      */
     private void connectNodes(List<SimplifiedNodeDomain> nodeInfo)
     {
             for(SimplifiedNodeDomain sn: nodeInfo)
             {
                     Node temp = getPrivateNode(sn.webId);
                     temp.setFold(getPrivateNode(sn.fold));
                     temp.setSurrogateFold(getPrivateNode(sn.surrogateFold));
                     temp.setInverseSurrogateFold(getPrivateNode(sn.inverseSurrogateFold));
                     temp.setState(NodeState.getNodeState(sn.state));
                     
                     for(int n: sn.neighbors)
                     {
                             temp.addNeighbor(getPrivateNode(n));
                     }
                     for(int n: sn.downPointers)
                     {
                             temp.addDownPointer(getPrivateNode(n));
                     }
                     for(int n: sn.upPointers)
                     {
                             temp.addUpPointer(getPrivateNode(n));
                     }
             }
     }
     /**
      * connects the nodes that were received by the database
      * @param int i which is the webId of the node we want to get
      * @return Node (or NULL_NODE if Node isn't in the HypeerWeb)
      * 
      * @pre i is a number and not null
      * @post <i>None</i>
      */
     private Node getPrivateNode(int i)
     {
              for(Node n:nodes)
              {
                      if(n.getWebId().getValue() == i)
                              return n;
              }
              return Node.NULL_NODE;
     }
     //cls
     private void generateKeys(Node node)
     {
         ObjectDB.getSingleton().store(node.get_key(),node);
     }
     public LocalObjectId get_key()
     {
         return globalId.getLocalObjectId();
     }
     
}